package com.edoovo.loginservice.application.config.auth;

import com.edoovo.loginservice.application.util.TimeUtils;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.security.KeyPair;
import java.util.Map;

@Configuration
@RequiredArgsConstructor
@SuppressWarnings("all")
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    private final String currentLoginDate = "current_login_date";
    private final String rememberMe = "remember_me";
    private final CustomBCryptPasswordEncoder customBCryptPasswordEncoder;
    private final AuthenticationManager authenticationManager;
    private final CustomUserDetailsService customUserDetailsService;

    @Override
    public void configure(
            AuthorizationServerSecurityConfigurer oauthServer)  {
        oauthServer.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(customUserDetailsService)
                .accessTokenConverter(accessTokenConverter())
                .authenticationManager(authenticationManager)
                .exceptionTranslator(webResponseExceptionTranslator());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    /**
     * final Set<String> scopes = new HashSet<>(accessToken.getScope());
     * scopes.add(customUserDetails.getRole());
     * DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
     * customAccessToken.setScope(scopes);
     * additionalInfo.put("role", customUserDetails.getRole());
     * accessToken.getScope().add(customUserDetails.getRole().toUpperCase());
     * <p>
     * ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(new HashMap<>());
     * return accessToken;
     *
     * @return JwtAccessTokenConverter
     */
    
	private JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                final Map<String, Object> additionalInfo = Maps.newHashMap();

                //todo 根据业务删除 start
                // Get additional information from requestParameters
                Map<String, String> requestParameters = authentication.getOAuth2Request().getRequestParameters();
                if (requestParameters.containsKey("id"))
                    additionalInfo.put("id", requestParameters.get("id"));
                if (requestParameters.containsKey(currentLoginDate))
                    additionalInfo.put(currentLoginDate, requestParameters.get(currentLoginDate));
                if (requestParameters.containsKey(rememberMe))
                    additionalInfo.put(rememberMe, requestParameters.get(rememberMe));

                Object principal = authentication.getUserAuthentication().getPrincipal();
                if (principal instanceof CustomUserDetails) {
                    CustomUserDetails customUserDetails = (CustomUserDetails) principal;
                    additionalInfo.put("id", customUserDetails.getId());
                    additionalInfo.put(currentLoginDate, Long.toString(TimeUtils.toEpochMilli(customUserDetails.getCurrentLoginDate())));
                } else {
                    customUserDetailsService.checkUserAccount(authentication.getName());
                }
                // todo end
                ((DefaultOAuth2AccessToken) accessToken)
                        .setAdditionalInformation(additionalInfo);
                return super.enhance(accessToken, authentication);
            }

            @Override
            public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
                OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
                // Regenerate OAuth2Authentication with additional information
                Map<String, String> requestParameters = oAuth2Authentication.getOAuth2Request().getRequestParameters();

                //todo 根据业务删除 start
                Map<String, String> additionalInfoParameters = Maps.newHashMap(requestParameters);
                if (map.containsKey("id"))
                    additionalInfoParameters.put("id", (String) map.get("id"));
                if (map.containsKey(currentLoginDate))
                    additionalInfoParameters.put(currentLoginDate, (String) map.get(currentLoginDate));
                if (map.containsKey(rememberMe))
                    additionalInfoParameters.put(rememberMe, (String) map.get(rememberMe));
                OAuth2Request request = new OAuth2Request(
                        additionalInfoParameters,
                        oAuth2Authentication.getOAuth2Request().getClientId(), oAuth2Authentication.getAuthorities(), Boolean.TRUE,
                        oAuth2Authentication.getOAuth2Request().getScope(), oAuth2Authentication.getOAuth2Request().getResourceIds(),
                        null, null, null);
                //todo end
                return new OAuth2Authentication(request, oAuth2Authentication.getUserAuthentication());
            }
        };
        converter.setKeyPair(getKeyPair());
        return converter;
    }


    @Bean
    KeyPair getKeyPair() {
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(new ClassPathResource("edoovo.jks"), "edoovotest".toCharArray());
        return keyStoreKeyFactory.getKeyPair("edoovo-jwt");

    }

    @SuppressWarnings({"unused"})
    private DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("clientid")
                .secret(customBCryptPasswordEncoder.encode("secret"))
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("user");
    }

    private WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                OAuth2Exception body = responseEntity.getBody();
                assert body != null;
                body.addAdditionalInformation("code", "400");
                HttpHeaders headers = new HttpHeaders();
                headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                // do something with header or response
                return new ResponseEntity<>(body, headers, responseEntity.getStatusCode());
            }
        };
    }

}